﻿using System;
using System.Collections.Generic;

namespace SharpObjects.Instantiation.Services
{
    public class ServiceFactory : IServiceFactory
    {
        public static IServiceFactory Instance { get; set; }
        
        private Dictionary<string, List<IServiceDescriptor>> serviceDescriptors = new Dictionary<string, List<IServiceDescriptor>>();
        private InstanceFactory instanceFactory = new InstanceFactory();

        protected ServiceFactory()
        {
            ServiceFactory.Instance = this;
        }

        public ServiceFactory(IServiceDescriptor [] serviceDescriptors)
        {
            Initialize(serviceDescriptors);
        }

        public void Initialize(IServiceDescriptor[] serviceDescriptors)
        {
            foreach(IServiceDescriptor serviceDescriptor in serviceDescriptors)
            {
                if(serviceDescriptor.TypeDescriptor.Type == null)
                {
                    serviceDescriptor.TypeDescriptor.Type = this.instanceFactory.GetTypeFromTypeDescriptor(serviceDescriptor.TypeDescriptor);
                }

                if(this.serviceDescriptors.ContainsKey(serviceDescriptor.ServiceId))
                {
                    this.serviceDescriptors[serviceDescriptor.ServiceId].Add(serviceDescriptor);
                }
                else
                {
                    List<IServiceDescriptor> descriptors = new List<IServiceDescriptor>();
                    descriptors.Add(serviceDescriptor);
                    this.serviceDescriptors.Add(serviceDescriptor.ServiceId, descriptors);
                }
            }

            PreInstantiate(serviceDescriptors);
        }

        protected void PreInstantiate(IServiceDescriptor [] serviceDescriptors)
        {
            foreach(IServiceDescriptor serviceDescriptor in serviceDescriptors)
            {
                if(serviceDescriptor.PreInstantiate)
                {
                    GetServiceFromServiceDescriptor<object>(serviceDescriptor, serviceDescriptor.ServiceVersion);

                    if(serviceDescriptor.PreInstantiateVersions != null)
                    {
                        foreach(string serviceVersion in serviceDescriptor.PreInstantiateVersions)
                        {
                            GetServiceFromServiceDescriptor<object>(serviceDescriptor, serviceVersion);
                        }
                    }
                }
            }
        }

        public virtual T GetService<T>(string serviceId = null, string serviceVersion = null)
        {
            IServiceDescriptor serviceDescriptor = FindServiceDescriptor<T>(serviceId, serviceVersion);
            return GetServiceFromServiceDescriptor<T>(serviceDescriptor, serviceVersion);
        }

        protected virtual T GetServiceFromServiceDescriptor<T>(IServiceDescriptor serviceDescriptor, string serviceVersion)
        {

            if(serviceDescriptor.IsSingleton)
            {
                if(serviceDescriptor.ServiceVersion != serviceVersion)
                {
                    serviceDescriptor = serviceDescriptor.Clone();
                    serviceDescriptor.ServiceVersion = serviceVersion;
                    serviceDescriptor.SingletonInstance = null;
                    this.serviceDescriptors[serviceDescriptor.ServiceId].Add(serviceDescriptor);
                }

                if(serviceDescriptor.SingletonInstance == null)
                {
                    serviceDescriptor.SingletonInstance = this.instanceFactory.CreateObject<T>(serviceDescriptor.TypeDescriptor);
                }

                return (T)serviceDescriptor.SingletonInstance;
            }
            else
            {
                return this.instanceFactory.CreateObject<T>(serviceDescriptor.TypeDescriptor);
            }
        }

        protected IServiceDescriptor FindServiceDescriptor<T>(string serviceId, string serviceVersion)
        {
            if (serviceId != null)
            {
                if (this.serviceDescriptors.ContainsKey(serviceId))
                {
                    IServiceDescriptor defaultServiceDescriptor = null;

                    foreach (IServiceDescriptor serviceDescriptor in this.serviceDescriptors[serviceId])
                    {
                        if (serviceDescriptor.ServiceVersion == serviceVersion)
                        {
                            return serviceDescriptor;
                        }

                        if (serviceDescriptor.ServiceVersion == null)
                        {
                            defaultServiceDescriptor = serviceDescriptor;
                        }
                    }

                    if (defaultServiceDescriptor != null)
                    {
                        return defaultServiceDescriptor;
                    }
                }
            }
            else
            {
                Type typeToFind = typeof (T);

                foreach(string serviceKey in this.serviceDescriptors.Keys)
                {
                    foreach (IServiceDescriptor serviceDescriptor in this.serviceDescriptors[serviceKey])
                    {
                        if(serviceDescriptor.TypeDescriptor.Type == null)
                        {
                            serviceDescriptor.TypeDescriptor.Type = this.instanceFactory.GetTypeFromTypeDescriptor(serviceDescriptor.TypeDescriptor);
                        }

                        if(serviceDescriptor.TypeDescriptor.Type == typeToFind)
                        {
                            return serviceDescriptor;
                        }
                    }
                }
            }

            throw new ArgumentException(string.Format("No service of type '{0}' with the ServiceId '{1}' and ServiceVersion '{2}' found", typeof(T).GetType().FullName, serviceId, serviceVersion ?? "null"));
        }


    }
}
